The dotdensity R package provides dot-density functions can be used with any kind of data, but it has been designed with hierarchical geographic data, like census data, in mind.

Getting the data

We will make use of the cancensus package to obtain census data on languages spoken at home in the City of Vancouver, including the areas to the immediate west.

#devtools::install_github('mountainmath/dotdensity')
library(dotdensity)
#devtools::install_github('mountainmath/cancensus')
library(cancensus)
# options(cancensus.api_key)='<your API key>'

Using the CensusMapper API tool we select the region and variables we need,

dataset='CA16'
regions=list(CT=c("9330069.01","9330069.02"),CSD=c("5915022","5915803")) #list(CMA="59933")
vectors=c("v_CA16_1355","v_CA16_1364","v_CA16_2060","v_CA16_2066","v_CA16_2153","v_CA16_1916","v_CA16_1937","v_CA16_1973","v_CA16_1727")

where we setttled for the City of Vancouver, together with Musqueam to and the UBC/UEL area.

We start out by defining a basemap on which to plot our data.

OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_7d530bb2f6912fbf45cde8f00d7e3134.geojson", layer: "OGRGeoJSON"
with 8 features
It has 14 fields

We have defined a convenience function prep_data that renames variables and computes the “Other” language categority as a catch-all for the lanugages we don’t explicitly list.

Armed with this function we can now read in census tract level data.

data_ct <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="CT") %>% prep_data
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_82d598f6dd4811b948d8cc7aaac03edd.geojson", layer: "OGRGeoJSON"
with 120 features
It has 11 fields

Mapping

The categories we want to map consist of all the loaded census language variables, except for the “Total” category that we want to replace with the computed “Other” category. We pick colours to represent these, decide on a scale (how many dots per person) to map as well as the opacity and size of each dot.

# Set the categorie we want to map. Those are the labels except we want to replace the "Total" with the "Other" column
categories=append(setdiff(attributes(data_ct)$census_labels$Detail,c("Total")),"Other")
colors=c("#a0a0a0", "#0000ff", "#ff0000", "#ffff00", "#00ff00", "#00ffff", "#ff00ff", "#660066", "#006600")
scale=25
alpha=0.75
size=1

Now all that’s left to do is to create the dot-density map. Dot-density maps are hard for many reasons. We have to randomly distribute the number of dots in each region. Often we scale the number of dots we want to draw so that one dot represents several people. This is what the scale parameter does in our function call. When the scale is greater than 1, we need to round to an integer number of dots. Using regular rounding may introduce systematic erros. For example, if the scale is 50 and one category we are mapping has many areas with 20 people. Regular rounding will round all of these to zero, and the visual effect is that none of these areas will appear to have any people of that category. To deal with this we can replace regular rounding with statistical rounding so that the total number of dots will be unbiased. The plot function in out dotdensity package has that built in.

dots.ct <- dot_density.compute_dots(geo_data = data_ct, categories = categories, scale=scale)
basemap + dot_density.dots_map(dots=dots.ct,alpha=alpha,size=size)

While the broad geographic patterns are clearly visible, the uniform distribution of dots in each census tract is very noticeable and gives the map an unnatural feel.

Refining the Map

We can remedy this by using dissemination area data, which we can query by simply replacing the changing parameter from “CT” to “DA” in the cancensus call.

data_da <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="DA") %>% prep_data
Reading vectors data from local cache.
Reading geo data from local cache.
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_63e21d24e30bf0c7ffe020b6f6208adc.geojson", layer: "OGRGeoJSON"
with 1013 features
It has 10 fields

However, dissemination level data tends to systematically under-count variables. That is because the data preparation with privacy and poor data quality suppression and statistical rounding near zero are not unbiased.

In our case that difference won’t be large as we can see

print(paste0("Missing counts at DA level: ",sum(data_ct$Mandarin)-sum(data_da$Mandarin)))
[1] "Missing counts at DA level: 215"

But it is a problem that we in genral should not ignore. We can remedy this by computing the undercount for the dissemination areas in each census tract and assigning the missing counts proportionally to the population in each area. This is exactly what another convenience function int he dotdensity package does.

data_da@data <- dot_density.proportional_re_aggregate(data=data_da@data,parent_data=data_ct@data,geo_match=setNames("GeoUID","CT_UID"),categories=categories,base="Population")
print(paste0("Missing after adjustment: ",sum(data_ct$Mandarin)-sum(data_da$Mandarin)))
[1] "Missing after adjustment: 0"

Plotting this we get the exact same number of dots as before, but with finer geographic distribution.

dots.da <- dot_density.compute_dots(geo_data = data_da, categories = categories, scale=scale)
basemap + dot_density.dots_map(dots=dots.da,alpha=alpha,size=size)

We still notice that dots are placed in weird spaces, for example in the Stanley Park and Pacific Spirit Park. We can refine the re-aggregaion process clustering the dots according to dissemination block level population counts.

data_db <- cancensus.load(geo_format='sp',labels='short',dataset=dataset, regions=regions, vectors=vectors,level="DB") 
OGR data source with driver: GeoJSON 
Source: "data_cache/CM_geo_64e0f25c6ce0caf01160ed4ce2d34c25.geojson", layer: "OGRGeoJSON"
with 4723 features
It has 10 fields
data_db@data <- dot_density.proportional_re_aggregate(data=data_db@data,parent_data=data_da@data,geo_match=setNames("GeoUID","DA_UID"),categories=categories,base="Population")
dots.db <- dot_density.compute_dots(geo_data = data_db, categories = categories, scale=scale)
basemap + dot_density.dots_map(dots=dots.db,alpha=alpha,size=size)

This now gives a map that is much closer to our everyday experience, and is intuitive and visually appealing. However, the better we get at placing dots and the fewer visual clues of the random processes used to produce these maps remain, readers may be lead to believe that the map has an accuracy that does not exist. The scaling, drawing 1 dot for every 25 people, is the only reminder that dot locations are approximate.

Takeway

The takeaway of this example is that having multiple levels of data available in R allows us to produce intuitive representation of multi-category data. The package almost automatically takes care of several pitfalls and challenges when producing dot-density maps, including statistical rounding for unbiased scaling, re-aggregating of data to recover under-counts at finer aggregation levels, as well as the geographic weighting by dissemination block population counts.

The convenience of using cancensus to pull in the data can’t be under-stated. In this example, we easily assembled mixed geographic levels, two CSDs and two CTs to cut out the area we were interested in and obtained select variables at three different aggregation levels without even breaking a sweat. And when sharing the code, we don’t need to share the data as a simple API call will import it.

It is straight-forward to adapt the example by changing the region parameter to almost instantly reproduce the map for other areas of interest, or change the lanugages we break out. This makes this a reproducible and very adaptable tool.

LS0tCnRpdGxlOiAiTGFuZ3VhZ2VzIFNwb2tlbiBhdCBIb21lIgphdXRob3I6ICJKZW5zIHZvbiBCZXJnbWFubiIKZGF0ZTogIjIwMTctMDgtMTAiCm91dHB1dDogaHRtbF9ub3RlYm9vawp2aWduZXR0ZTogPgogICVcVmlnbmV0dGVJbmRleEVudHJ5e1ZpZ25ldHRlIFRpdGxlfQogICVcVmlnbmV0dGVFbmdpbmV7a25pdHI6OnJtYXJrZG93bn0KICAlXFZpZ25ldHRlRW5jb2Rpbmd7VVRGLTh9Ci0tLQoKVGhlIFtgZG90ZGVuc2l0eWAgUiBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vbW91bnRhaW5NYXRoL2RvdGRlbnNpdHkpIHByb3ZpZGVzIGRvdC1kZW5zaXR5IGZ1bmN0aW9ucyBjYW4gYmUgdXNlZCB3aXRoIGFueSBraW5kIG9mIGRhdGEsIGJ1dCBpdCBoYXMgYmVlbiBkZXNpZ25lZCB3aXRoIGhpZXJhcmNoaWNhbApnZW9ncmFwaGljIGRhdGEsIGxpa2UgY2Vuc3VzIGRhdGEsIGluIG1pbmQuIAoKIyMgR2V0dGluZyB0aGUgZGF0YQpXZSB3aWxsIG1ha2UgdXNlIG9mIHRoZSBbY2FuY2Vuc3VzXShodHRwczovL2dpdGh1Yi5jb20vbW91bnRhaW5NYXRoL2NhbmNlbnN1cykgcGFja2FnZSB0byBvYnRhaW4gY2Vuc3VzCmRhdGEgb24gbGFuZ3VhZ2VzIHNwb2tlbiBhdCBob21lIGluIHRoZSBDaXR5IG9mIFZhbmNvdXZlciwgaW5jbHVkaW5nIHRoZSBhcmVhcyB0byB0aGUgaW1tZWRpYXRlIHdlc3QuCgpgYGB7cn0KI2RldnRvb2xzOjppbnN0YWxsX2dpdGh1YignbW91bnRhaW5tYXRoL2RvdGRlbnNpdHknKQpsaWJyYXJ5KGRvdGRlbnNpdHkpCiNkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoJ21vdW50YWlubWF0aC9jYW5jZW5zdXMnKQpsaWJyYXJ5KGNhbmNlbnN1cykKIyBvcHRpb25zKGNhbmNlbnN1cy5hcGlfa2V5KT0nPHlvdXIgQVBJIGtleT4nCmBgYAoKCgpVc2luZyB0aGUgW0NlbnN1c01hcHBlciBBUEkgdG9vbF0oaHR0cHM6Ly9jZW5zdXNtYXBwZXIuY2EvYXBpL0NBMTYpIHdlIHNlbGVjdCB0aGUgcmVnaW9uIGFuZAp2YXJpYWJsZXMgd2UgbmVlZCwKYGBge3J9CmRhdGFzZXQ9J0NBMTYnCnJlZ2lvbnM9bGlzdChDVD1jKCI5MzMwMDY5LjAxIiwiOTMzMDA2OS4wMiIpLENTRD1jKCI1OTE1MDIyIiwiNTkxNTgwMyIpKSAjbGlzdChDTUE9IjU5OTMzIikKdmVjdG9ycz1jKCJ2X0NBMTZfMTM1NSIsInZfQ0ExNl8xMzY0Iiwidl9DQTE2XzIwNjAiLCJ2X0NBMTZfMjA2NiIsInZfQ0ExNl8yMTUzIiwidl9DQTE2XzE5MTYiLCJ2X0NBMTZfMTkzNyIsInZfQ0ExNl8xOTczIiwidl9DQTE2XzE3MjciKQoKCmBgYAp3aGVyZSB3ZSBzZXR0dGxlZCBmb3IgdGhlIENpdHkgb2YgVmFuY291dmVyLCB0b2dldGhlciB3aXRoIE11c3F1ZWFtIHRvIGFuZCB0aGUgVUJDL1VFTCBhcmVhLgoKV2Ugc3RhcnQgb3V0IGJ5IGRlZmluaW5nIGEgYGJhc2VtYXBgIG9uIHdoaWNoIHRvIHBsb3Qgb3VyIGRhdGEuCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQp0aGVtZV9vcHRzPC1saXN0KGdncGxvdDI6OnRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9yZWN0KGZpbGwgPSAnbGlnaHQgYmx1ZScsIGNvbG91ciA9IE5BKSwKICAgICAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X3JlY3QoZmlsbD0ibGlnaHQgZ3JleSIsCiAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT0xLGxpbmV0eXBlPSJzb2xpZCIsY29sb3I9ImJsYWNrIiksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy5saW5lID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueSA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemU9MjIpKSkKCmJhc2VfZ2VvbSA8LSBjYW5jZW5zdXMubG9hZChnZW9fZm9ybWF0PSdzcCcsZGF0YXNldD1kYXRhc2V0LCByZWdpb25zPXJlZ2lvbnMsIGxldmVsPSJSZWdpb25zIikKCmJhc2VtYXAgPC0gICBnZ3Bsb3QyOjpnZ3Bsb3QoYmFzZV9nZW9tKSArCiAgICBnZ3Bsb3QyOjpnZW9tX3BvbHlnb24oZ2dwbG90Mjo6YWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCksIGZpbGwgPSAid2hpdGUiLCBzaXplPTAuMSkgKwogICAgI2dncGxvdDI6Omdlb21fcG9seWdvbihnZ3Bsb3QyOjphZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSwgY29sb3VyID0gIiMyMjIyMjIiLCBmaWxsID0gIndoaXRlIiwgc2l6ZT0wLjEpICsKICAgIGdncGxvdDI6Omd1aWRlcyhjb2xvdXIgPSBnZ3Bsb3QyOjpndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIpKSkgKwogICAgZ2dwbG90Mjo6bGFicyhjb2xvciA9ICJsYWJlbCIsY2FwdGlvbj0iU291cmNlOiBTdGF0Q2FuIENlbnN1cyAyMDE2IHZpYSBjYW5jZW5zdXMgJiBDZW5zdXNNYXBwZXIuY2EiKSArCiAgICBnZ3Bsb3QyOjpjb29yZF9tYXAocHJvamVjdGlvbj0ibGFtYmVydCIsIGxhdDA9NDksIGxhdD00OS40KSArCiAgICB0aGVtZV9vcHRzCmJhc2VtYXAKYGBgCgoKV2UgaGF2ZSBkZWZpbmVkIGEgY29udmVuaWVuY2UgZnVuY3Rpb24gYHByZXBfZGF0YWAgdGhhdCByZW5hbWVzIHZhcmlhYmxlcyBhbmQgY29tcHV0ZXMgdGhlICJPdGhlciIgbGFuZ3VhZ2UgY2F0ZWdvcml0eSBhcyBhIGNhdGNoLWFsbCBmb3IgdGhlIGxhbnVnYWdlcyB3ZSBkb24ndCBleHBsaWNpdGx5IGxpc3QuCmBgYHtyLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9VFJVRSwgaW5jbHVkZT1GQUxTRX0KIyByZW5hbWUgY29sdW1ucyBmb3IgYmV0dGVyIHJlYWRhYmlsaXR5IGFuZCBjb21wdXRlIGFnZ3JlZ2F0ZXMKcHJlcF9kYXRhIDwtIGZ1bmN0aW9uKGdlbyl7CiAgZGF0YSA8LSBnZW9AZGF0YSAlPiUgcmVwbGFjZShpcy5uYSguKSwgMCkKICBsYWJlbHMgPC0gYXR0cmlidXRlcyhnZW8pJGNlbnN1c19sYWJlbHMKICAKICAjIGluIGxhYmVscywgZHJvcCB0ZXh0IGluIHBhcmVudGhlc2lzIGZvciBicmV2aXR5CiAgbGFiZWxzJERldGFpbFtncmVwKCJcXHcrXCBcXCguKlxcKSIsbGFiZWxzJERldGFpbCldIDwtICBnc3ViKCIoXFx3KylcIFxcKC4qXFwpIiwiXFwxIixsYWJlbHMkRGV0YWlsW2dyZXAoIlxcdytcIFxcKC4qXFwpIixsYWJlbHMkRGV0YWlsKV0pCiAgCiAgbGFiZWxzJERldGFpbFtncmVwKCJUb3RhbCIsbGFiZWxzJERldGFpbCldPSJUb3RhbCIKICBsYWJlbHMkRGV0YWlsW2dyZXAoIkVuZ2xpc2ggYW5kIixsYWJlbHMkRGV0YWlsKV09IkVuZ2xpc2ggKyBPdGhlciIKICAKICB0b3RhbD1sYWJlbHMkVmVjdG9yW2xhYmVscyREZXRhaWw9PSJUb3RhbCJdCiAgb3RoZXJzPXNldGRpZmYobGFiZWxzJFZlY3RvcixjKHRvdGFsKSkKICAKICBkYXRhJE90aGVyIDwtIGRhdGFbW3RvdGFsXV0KICBvbGRfbmFtZXM9bmFtZXMoZGF0YSkKICBmb3IgKGkgaW4gMTpsZW5ndGgob3RoZXJzKSkgewogICAgZGF0YSRPdGhlciA8LSBkYXRhJE90aGVyIC0gdW5saXN0KGRhdGEgJT4lIHNlbGVjdF8oYXMubmFtZShvdGhlcnNbaV0pKSkKICAgIG9sZF9uYW1lc1tvbGRfbmFtZXM9PW90aGVyc1tpXV0gPC0gbGFiZWxzJERldGFpbFtsYWJlbHMkVmVjdG9yPT1vdGhlcnNbaV1dCiAgfQogIG5hbWVzKGRhdGEpPW9sZF9uYW1lcwoKICBnZW9AZGF0YSA8LSBkYXRhCiAgYXR0cmlidXRlcyhnZW8pJGNlbnN1c19sYWJlbHMgPC0gbGFiZWxzCiAgcmV0dXJuKGdlbykKfQpgYGAKQXJtZWQgd2l0aCB0aGlzIGZ1bmN0aW9uIHdlIGNhbiBub3cgcmVhZCBpbiBjZW5zdXMgdHJhY3QgbGV2ZWwgZGF0YS4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpkYXRhX2N0IDwtIGNhbmNlbnN1cy5sb2FkKGdlb19mb3JtYXQ9J3NwJyxsYWJlbHM9J3Nob3J0JyxkYXRhc2V0PWRhdGFzZXQsIHJlZ2lvbnM9cmVnaW9ucywgdmVjdG9ycz12ZWN0b3JzLGxldmVsPSJDVCIpICU+JSBwcmVwX2RhdGEKCmBgYAoKIyMgTWFwcGluZwoKVGhlIGNhdGVnb3JpZXMgd2Ugd2FudCB0byBtYXAgY29uc2lzdCBvZiBhbGwgdGhlIGxvYWRlZCBjZW5zdXMgbGFuZ3VhZ2UgdmFyaWFibGVzLCBleGNlcHQgZm9yIHRoZSAiVG90YWwiIGNhdGVnb3J5IHRoYXQgd2Ugd2FudCB0byByZXBsYWNlIHdpdGggdGhlIGNvbXB1dGVkICJPdGhlciIgY2F0ZWdvcnkuIFdlIHBpY2sgY29sb3VycyB0byByZXByZXNlbnQgdGhlc2UsIGRlY2lkZSBvbiBhIHNjYWxlIChob3cgbWFueSBkb3RzIHBlciBwZXJzb24pIHRvIG1hcCBhcyB3ZWxsIGFzIHRoZSBvcGFjaXR5IGFuZCBzaXplIG9mIGVhY2ggZG90LgpgYGB7cn0KIyBTZXQgdGhlIGNhdGVnb3JpZSB3ZSB3YW50IHRvIG1hcC4gVGhvc2UgYXJlIHRoZSBsYWJlbHMgZXhjZXB0IHdlIHdhbnQgdG8gcmVwbGFjZSB0aGUgIlRvdGFsIiB3aXRoIHRoZSAiT3RoZXIiIGNvbHVtbgpjYXRlZ29yaWVzPWFwcGVuZChzZXRkaWZmKGF0dHJpYnV0ZXMoZGF0YV9jdCkkY2Vuc3VzX2xhYmVscyREZXRhaWwsYygiVG90YWwiKSksIk90aGVyIikKY29sb3JzPWMoIiNhMGEwYTAiLCAiIzAwMDBmZiIsICIjZmYwMDAwIiwgIiNmZmZmMDAiLCAiIzAwZmYwMCIsICIjMDBmZmZmIiwgIiNmZjAwZmYiLCAiIzY2MDA2NiIsICIjMDA2NjAwIikKc2NhbGU9MjUKYWxwaGE9MC43NQpzaXplPTEKCmBgYAoKCmBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIHNldCBtYXAgdGl0bGUgdXNpbmcgdGhlIHNjYWxlIGFuZCBjb2xvdXIgdmFsdWVzCnRpdGxlPXBhc3RlMCgiTGFuZ3VhZ2VzIFNwb2tlbiBhdCBIb21lXG4xIGRvdCA9ICIsc2NhbGUsIiBwZW9wbGUiKQpiYXNlbWFwIDwtIGJhc2VtYXAgKyBnZ3Bsb3QyOjpzY2FsZV9jb2xvdXJfbWFudWFsKHRpdGxlLHZhbHVlcyA9IGNvbG9ycykgCmBgYAoKTm93IGFsbCB0aGF0J3MgbGVmdCB0byBkbyBpcyB0byBjcmVhdGUgdGhlIGRvdC1kZW5zaXR5IG1hcC4gRG90LWRlbnNpdHkgbWFwcyBhcmUgaGFyZCBmb3IgbWFueSByZWFzb25zLgpXZSBoYXZlIHRvIHJhbmRvbWx5IGRpc3RyaWJ1dGUgdGhlIG51bWJlciBvZiBkb3RzIGluIGVhY2ggcmVnaW9uLiBPZnRlbiB3ZSBzY2FsZSB0aGUgbnVtYmVyIG9mIGRvdHMKd2Ugd2FudCB0byBkcmF3IHNvIHRoYXQgb25lIGRvdCByZXByZXNlbnRzIHNldmVyYWwgcGVvcGxlLiBUaGlzIGlzIHdoYXQgdGhlICpzY2FsZSogcGFyYW1ldGVyIGRvZXMgaW4gb3VyIGZ1bmN0aW9uIGNhbGwuIFdoZW4gdGhlIHNjYWxlIGlzIGdyZWF0ZXIgdGhhbiAxLCB3ZSBuZWVkIHRvIHJvdW5kIHRvIGFuIGludGVnZXIgbnVtYmVyIG9mIGRvdHMuIFVzaW5nIHJlZ3VsYXIgcm91bmRpbmcgbWF5IGludHJvZHVjZSBzeXN0ZW1hdGljIGVycm9zLiBGb3IgZXhhbXBsZSwgaWYgdGhlIHNjYWxlIGlzIDUwIGFuZCBvbmUgY2F0ZWdvcnkgd2UgYXJlIG1hcHBpbmcgaGFzIG1hbnkgYXJlYXMgd2l0aCAyMCBwZW9wbGUuIFJlZ3VsYXIgcm91bmRpbmcgd2lsbCByb3VuZCBhbGwgb2YgdGhlc2UgdG8gemVybywgYW5kIHRoZSB2aXN1YWwgZWZmZWN0IGlzIHRoYXQgbm9uZSBvZiB0aGVzZSBhcmVhcyB3aWxsIGFwcGVhciB0byBoYXZlIGFueSBwZW9wbGUgb2YgdGhhdCBjYXRlZ29yeS4gVG8gZGVhbCB3aXRoIHRoaXMgd2UgY2FuIHJlcGxhY2UgcmVndWxhciByb3VuZGluZyB3aXRoIHN0YXRpc3RpY2FsIHJvdW5kaW5nIHNvIHRoYXQgdGhlIHRvdGFsIG51bWJlciBvZiBkb3RzIHdpbGwgYmUgdW5iaWFzZWQuIFRoZSAqcGxvdCogZnVuY3Rpb24gaW4gb3V0IGBkb3RkZW5zaXR5YCBwYWNrYWdlIGhhcyB0aGF0IGJ1aWx0IGluLgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpkb3RzLmN0IDwtIGRvdF9kZW5zaXR5LmNvbXB1dGVfZG90cyhnZW9fZGF0YSA9IGRhdGFfY3QsIGNhdGVnb3JpZXMgPSBjYXRlZ29yaWVzLCBzY2FsZT1zY2FsZSkKYmFzZW1hcCArIGRvdF9kZW5zaXR5LmRvdHNfbWFwKGRvdHM9ZG90cy5jdCxhbHBoYT1hbHBoYSxzaXplPXNpemUpCmBgYAoKV2hpbGUgdGhlIGJyb2FkIGdlb2dyYXBoaWMgcGF0dGVybnMgYXJlIGNsZWFybHkgdmlzaWJsZSwgdGhlIHVuaWZvcm0gZGlzdHJpYnV0aW9uIG9mIGRvdHMgaW4gZWFjaCBjZW5zdXMgdHJhY3QgaXMgdmVyeSBub3RpY2VhYmxlIGFuZCBnaXZlcyB0aGUgbWFwIGFuIHVubmF0dXJhbCBmZWVsLgoKIyMgUmVmaW5pbmcgdGhlIE1hcAoKV2UgY2FuIHJlbWVkeSB0aGlzIGJ5IHVzaW5nIGRpc3NlbWluYXRpb24gYXJlYSBkYXRhLCB3aGljaCB3ZSBjYW4gcXVlcnkgYnkgc2ltcGx5IHJlcGxhY2luZyB0aGUgY2hhbmdpbmcgcGFyYW1ldGVyIGZyb20gIkNUIiB0byAiREEiIGluIHRoZSBgY2FuY2Vuc3VzYCBjYWxsLgpgYGB7cn0KZGF0YV9kYSA8LSBjYW5jZW5zdXMubG9hZChnZW9fZm9ybWF0PSdzcCcsbGFiZWxzPSdzaG9ydCcsZGF0YXNldD1kYXRhc2V0LCByZWdpb25zPXJlZ2lvbnMsIHZlY3RvcnM9dmVjdG9ycyxsZXZlbD0iREEiKSAlPiUgcHJlcF9kYXRhCmBgYAoKSG93ZXZlciwgZGlzc2VtaW5hdGlvbiBsZXZlbCBkYXRhIHRlbmRzIHRvIHN5c3RlbWF0aWNhbGx5IHVuZGVyLWNvdW50IHZhcmlhYmxlcy4gVGhhdCBpcyBiZWNhdXNlIHRoZSBkYXRhIHByZXBhcmF0aW9uIHdpdGggcHJpdmFjeSBhbmQgcG9vciBkYXRhIHF1YWxpdHkgc3VwcHJlc3Npb24gYW5kIHN0YXRpc3RpY2FsIHJvdW5kaW5nIG5lYXIgemVybyBhcmUgbm90IHVuYmlhc2VkLiAKCkluIG91ciBjYXNlIHRoYXQgZGlmZmVyZW5jZSB3b24ndCBiZSBsYXJnZSBhcyB3ZSBjYW4gc2VlIApgYGB7cn0KcHJpbnQocGFzdGUwKCJNaXNzaW5nIGNvdW50cyBhdCBEQSBsZXZlbDogIixzdW0oZGF0YV9jdCRNYW5kYXJpbiktc3VtKGRhdGFfZGEkTWFuZGFyaW4pKSkKYGBgCkJ1dCBpdCBpcyBhIHByb2JsZW0gdGhhdCB3ZSBpbiBnZW5yYWwgc2hvdWxkIG5vdCBpZ25vcmUuCldlIGNhbiByZW1lZHkgdGhpcyBieSBjb21wdXRpbmcgdGhlIHVuZGVyY291bnQgZm9yIHRoZSBkaXNzZW1pbmF0aW9uIGFyZWFzIGluIGVhY2ggY2Vuc3VzIHRyYWN0IGFuZCBhc3NpZ25pbmcgdGhlIG1pc3NpbmcgY291bnRzIHByb3BvcnRpb25hbGx5IHRvIHRoZSBwb3B1bGF0aW9uIGluIGVhY2ggYXJlYS4gVGhpcyBpcyBleGFjdGx5IHdoYXQgYW5vdGhlciBjb252ZW5pZW5jZSBmdW5jdGlvbiBpbnQgaGUgYGRvdGRlbnNpdHlgIHBhY2thZ2UgZG9lcy4KCmBgYHtyfQpkYXRhX2RhQGRhdGEgPC0gZG90X2RlbnNpdHkucHJvcG9ydGlvbmFsX3JlX2FnZ3JlZ2F0ZShkYXRhPWRhdGFfZGFAZGF0YSxwYXJlbnRfZGF0YT1kYXRhX2N0QGRhdGEsZ2VvX21hdGNoPXNldE5hbWVzKCJHZW9VSUQiLCJDVF9VSUQiKSxjYXRlZ29yaWVzPWNhdGVnb3JpZXMsYmFzZT0iUG9wdWxhdGlvbiIpCnByaW50KHBhc3RlMCgiTWlzc2luZyBhZnRlciBhZGp1c3RtZW50OiAiLHN1bShkYXRhX2N0JE1hbmRhcmluKS1zdW0oZGF0YV9kYSRNYW5kYXJpbikpKQpgYGAKClBsb3R0aW5nIHRoaXMgd2UgZ2V0IHRoZSBleGFjdCBzYW1lIG51bWJlciBvZiBkb3RzIGFzIGJlZm9yZSwgYnV0IHdpdGggZmluZXIgZ2VvZ3JhcGhpYyBkaXN0cmlidXRpb24uCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQpkb3RzLmRhIDwtIGRvdF9kZW5zaXR5LmNvbXB1dGVfZG90cyhnZW9fZGF0YSA9IGRhdGFfZGEsIGNhdGVnb3JpZXMgPSBjYXRlZ29yaWVzLCBzY2FsZT1zY2FsZSkKYmFzZW1hcCArIGRvdF9kZW5zaXR5LmRvdHNfbWFwKGRvdHM9ZG90cy5kYSxhbHBoYT1hbHBoYSxzaXplPXNpemUpCmBgYAoKV2Ugc3RpbGwgbm90aWNlIHRoYXQgZG90cyBhcmUgcGxhY2VkIGluIHdlaXJkIHNwYWNlcywgZm9yIGV4YW1wbGUgaW4gdGhlIFN0YW5sZXkgUGFyayBhbmQgUGFjaWZpYyBTcGlyaXQgUGFyay4gV2UgY2FuIHJlZmluZSB0aGUgcmUtYWdncmVnYWlvbiBwcm9jZXNzIGNsdXN0ZXJpbmcgdGhlIGRvdHMgYWNjb3JkaW5nIHRvIGRpc3NlbWluYXRpb24gYmxvY2sgbGV2ZWwgcG9wdWxhdGlvbiBjb3VudHMuCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkYXRhX2RiIDwtIGNhbmNlbnN1cy5sb2FkKGdlb19mb3JtYXQ9J3NwJyxsYWJlbHM9J3Nob3J0JyxkYXRhc2V0PWRhdGFzZXQsIHJlZ2lvbnM9cmVnaW9ucywgdmVjdG9ycz12ZWN0b3JzLGxldmVsPSJEQiIpIApkYXRhX2RiQGRhdGEgPC0gZG90X2RlbnNpdHkucHJvcG9ydGlvbmFsX3JlX2FnZ3JlZ2F0ZShkYXRhPWRhdGFfZGJAZGF0YSxwYXJlbnRfZGF0YT1kYXRhX2RhQGRhdGEsZ2VvX21hdGNoPXNldE5hbWVzKCJHZW9VSUQiLCJEQV9VSUQiKSxjYXRlZ29yaWVzPWNhdGVnb3JpZXMsYmFzZT0iUG9wdWxhdGlvbiIpCgpkb3RzLmRiIDwtIGRvdF9kZW5zaXR5LmNvbXB1dGVfZG90cyhnZW9fZGF0YSA9IGRhdGFfZGIsIGNhdGVnb3JpZXMgPSBjYXRlZ29yaWVzLCBzY2FsZT1zY2FsZSkKYmFzZW1hcCArIGRvdF9kZW5zaXR5LmRvdHNfbWFwKGRvdHM9ZG90cy5kYixhbHBoYT1hbHBoYSxzaXplPXNpemUpCmBgYApUaGlzIG5vdyBnaXZlcyBhIG1hcCB0aGF0IGlzIG11Y2ggY2xvc2VyIHRvIG91ciBldmVyeWRheSBleHBlcmllbmNlLCBhbmQgaXMgaW50dWl0aXZlIGFuZCB2aXN1YWxseSBhcHBlYWxpbmcuIEhvd2V2ZXIsIHRoZSBiZXR0ZXIgd2UgZ2V0IGF0IHBsYWNpbmcgZG90cyBhbmQgdGhlIGZld2VyIHZpc3VhbCBjbHVlcyBvZiB0aGUgcmFuZG9tIHByb2Nlc3NlcyB1c2VkIHRvIHByb2R1Y2UgdGhlc2UgbWFwcyByZW1haW4sIHJlYWRlcnMgbWF5IGJlIGxlYWQgdG8gYmVsaWV2ZSB0aGF0IHRoZSBtYXAgaGFzIGFuIGFjY3VyYWN5IHRoYXQgZG9lcyBub3QgZXhpc3QuIFRoZSBzY2FsaW5nLCBkcmF3aW5nIDEgZG90IGZvciBldmVyeSAyNSBwZW9wbGUsIGlzIHRoZSBvbmx5IHJlbWluZGVyIHRoYXQgZG90IGxvY2F0aW9ucyBhcmUgYXBwcm94aW1hdGUuCgojIyMgVGFrZXdheQpUaGUgdGFrZWF3YXkgb2YgdGhpcyBleGFtcGxlIGlzIHRoYXQgaGF2aW5nIG11bHRpcGxlIGxldmVscyBvZiBkYXRhIGF2YWlsYWJsZSBpbiBSIGFsbG93cyB1cyB0byBwcm9kdWNlIGludHVpdGl2ZSByZXByZXNlbnRhdGlvbiBvZiBtdWx0aS1jYXRlZ29yeSBkYXRhLiBUaGUgcGFja2FnZSBhbG1vc3QgYXV0b21hdGljYWxseSB0YWtlcyBjYXJlIG9mIHNldmVyYWwgcGl0ZmFsbHMgYW5kIGNoYWxsZW5nZXMgd2hlbiBwcm9kdWNpbmcgZG90LWRlbnNpdHkgbWFwcywgaW5jbHVkaW5nIHN0YXRpc3RpY2FsIHJvdW5kaW5nIGZvciB1bmJpYXNlZCBzY2FsaW5nLCByZS1hZ2dyZWdhdGluZyBvZiBkYXRhIHRvIHJlY292ZXIgdW5kZXItY291bnRzIGF0IGZpbmVyIGFnZ3JlZ2F0aW9uIGxldmVscywgYXMgd2VsbCBhcyB0aGUgZ2VvZ3JhcGhpYyB3ZWlnaHRpbmcgYnkgZGlzc2VtaW5hdGlvbiBibG9jayBwb3B1bGF0aW9uIGNvdW50cy4KClRoZSBjb252ZW5pZW5jZSBvZiB1c2luZyBgY2FuY2Vuc3VzYCB0byBwdWxsIGluIHRoZSBkYXRhIGNhbid0IGJlIHVuZGVyLXN0YXRlZC4gSW4gdGhpcyBleGFtcGxlLCB3ZSBlYXNpbHkgYXNzZW1ibGVkIG1peGVkIGdlb2dyYXBoaWMgbGV2ZWxzLCB0d28gQ1NEcyBhbmQgdHdvIENUcyB0byBjdXQgb3V0IHRoZSBhcmVhIHdlIHdlcmUgaW50ZXJlc3RlZCBpbiBhbmQgb2J0YWluZWQgc2VsZWN0IHZhcmlhYmxlcyBhdCB0aHJlZSBkaWZmZXJlbnQgYWdncmVnYXRpb24gbGV2ZWxzIHdpdGhvdXQgZXZlbiBicmVha2luZyBhIHN3ZWF0LiBBbmQgd2hlbiBzaGFyaW5nIHRoZSBjb2RlLCB3ZSBkb24ndCBuZWVkIHRvIHNoYXJlIHRoZSBkYXRhIGFzIGEgc2ltcGxlIEFQSSBjYWxsIHdpbGwgaW1wb3J0IGl0LgoKSXQgaXMgc3RyYWlnaHQtZm9yd2FyZCB0byBhZGFwdCB0aGUgZXhhbXBsZSBieSBjaGFuZ2luZyB0aGUgcmVnaW9uIHBhcmFtZXRlciB0byBhbG1vc3QgaW5zdGFudGx5IHJlcHJvZHVjZSB0aGUgbWFwIGZvciBvdGhlciBhcmVhcyBvZiBpbnRlcmVzdCwgb3IgY2hhbmdlIHRoZSBsYW51Z2FnZXMgd2UgYnJlYWsgb3V0LiBUaGlzIG1ha2VzIHRoaXMgYSByZXByb2R1Y2libGUgYW5kIHZlcnkgYWRhcHRhYmxlIHRvb2wuCg==